﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
 
namespace Organized
{
    // Post processor that applies a Gaussian blur to an image or the frame buffer
    public class GaussianBlur : PostProcessor
    {
        // Offsets and wieghts for horizontal and vertical blurs
        Vector2[] sampleOffsetsH = new Vector2[15];
        float[] sampleWeightsH = new float[15];
 
        Vector2[] sampleOffsetsV = new Vector2[15];
        float[] sampleWeightsV = new float[15];
 
        // Constructors load Effect called GaussianBlur.fx
        public GaussianBlur(int Width, int Height) 
            : base(Engine.Content.Load<Effect>("Content/Effects/BlurEffect"), Width, Height) 
        {
            Setup(Width, Height);
        }
 
        public GaussianBlur(int Width, int Height, GameScreen Parent)
            : base(Engine.Content.Load<Effect>("Content/Effects/BlurEffect"), Width, Height, Parent) 
        {
            Setup(Width, Height);
        }
 
        // Calculate the weights and offsets based on width and height
        void Setup(int Width, int Height)
        {
            Vector2 texelSize = new Vector2(1f / Width, 1f / Height);
 
            SetBlurParameters(texelSize.X, 0, ref sampleOffsetsH, ref sampleWeightsH);
            SetBlurParameters(0, texelSize.Y, ref sampleOffsetsV, ref sampleWeightsV);
        }
 
        // Borrowed from "Shadow Mapping" by Andrew Joli
        // http://www.ziggyware.com/readarticle.php?article_id=161
        void SetBlurParameters(float dx, float dy, ref Vector2[] vSampleOffsets, ref float[] fSampleWeights)
        {
            // The first sample always has a zero offset.
            fSampleWeights[0] = ComputeGaussian(0);
            vSampleOffsets[0] = new Vector2(0);
 
            // Maintain a sum of all the weighting values.
            float totalWeights = fSampleWeights[0];
 
            // Add pairs of additional sample taps, positioned
            // along a line in both directions from the center.
            for (int i = 0; i < 15 / 2; i++)
            {
                // Store weights for the positive and negative taps.
                float weight = ComputeGaussian(i + 1);
 
                fSampleWeights[i * 2 + 1] = weight;
                fSampleWeights[i * 2 + 2] = weight;
 
                totalWeights += weight * 2;
 
                // The 1.5 offset kicks things off by
                // positioning us nicely in between two texels.
                float sampleOffset = i * 2 + 1.5f;
 
                Vector2 delta = new Vector2(dx, dy) * sampleOffset;
 
                // Store texture coordinate offsets for the positive and negative taps.
                vSampleOffsets[i * 2 + 1] = delta;
                vSampleOffsets[i * 2 + 2] = -delta;
            }
 
            // Normalize the list of sample weightings, so they will always sum to one.
            for (int i = 0; i < fSampleWeights.Length; i++)
                fSampleWeights[i] /= totalWeights;
        }
 
        // Borrowed from "Shadow Mapping" by Andrew Joli
        // http://www.ziggyware.com/readarticle.php?article_id=161
        private float ComputeGaussian(float n)
        {
            float theta = 2.0f + float.Epsilon;
            return theta = (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) * Math.Exp(-(n * n) / (2 * theta * theta)));
        }
 
        // Applies the post processor to the texture in the specified direction
        public void Draw(GaussianBlurDirection Direction, Texture2D Input)
        {
            this.Input = Input;
            SetParameters(Direction);
            base.Draw();
        }
 
        // Applies Gaussian Blur to frame buffer
        public override void Draw()
        {
            GetInputFromFrameBuffer(); // Set Input texture
            Engine.GraphicsDevice.Clear(Color.Black);
            SetParameters(GaussianBlurDirection.Horizontal); // Set horizontal parameters
            base.Draw(); // Apply blur
 
            GetInputFromFrameBuffer(); // Set Input texture again
            Engine.GraphicsDevice.Clear(Color.Black);
            SetParameters(GaussianBlurDirection.Vertical); // Set vertical parameters
            base.Draw(); // Apply blur
        }
 
        // Set blur parameters to effect
        void SetParameters(GaussianBlurDirection Direction)
        {
            if (Direction == GaussianBlurDirection.Horizontal)
            {
                Effect.Parameters["sampleWeights"].SetValue(sampleWeightsH);
                Effect.Parameters["sampleOffsets"].SetValue(sampleOffsetsH);
            }
            else
            {
                Effect.Parameters["sampleWeights"].SetValue(sampleWeightsV);
                Effect.Parameters["sampleOffsets"].SetValue(sampleOffsetsV);
            }
        }        
    }
 
    // Direction of Gaussian Blur: Horizontal or Vertical
    public enum GaussianBlurDirection { Horizontal, Vertical };
}